//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//

/* EONS requires LOCATIONUPDATES */
#ifdef RIL_ENABLE_EONS
#ifdef RIL_NO_CREG_LOCATION_UPDATES
#error RIL_ENABLE_EONS requires RIL_NO_CREG_LOCATION_UPDATES not set
#endif
#endif

/* atcmd */
#include <precomp.h>
#include <atcmd.h>

#include <eons.h>

/* for RIL_REGISTRY_ROOT and RIL_SECURE_REGISTRY_KEY */
#include <ril.h>

/* enable this to vigorously check EFopl and EFpnn coherency */
#if 0
#define CHECK
#endif

/****************************************************************************/
/*** start of helper ATCmd **************************************************/

class CRSMStatus : public ATCmd
{
public:
    CRSMStatus( DWORD dwAddress )
    {
        m_dwAddress = dwAddress;
        memset( &m_rsrs, 0, sizeof(m_rsrs) );
        m_rsrs.dwSize = sizeof(m_rsrs);
    }
    virtual ~CRSMStatus()
    {
    }
    HRESULT Read()
    {
        HRESULT hr = E_FAIL;

        char *szCmd = NULL;
        char buf[128];

        /* based on misc.cpp:RILDrv_GetSimRecordStatus() */
        /* simplied for use in the EONS context */

        _snprintfz( buf, ARRAY_LENGTH(buf), "AT+CRSM=%u,%u,%u,%u,%u",
#ifdef OEM1_DRIVER
                    192,
#else
                    242,
#endif
                    m_dwAddress, 0, 0, 15 );

        BOOL fOk = ComposeCmdWithByteArray( buf, NULL, 0, "\r", szCmd );
        if ( fOk )
        {
            hr = Execute( szCmd );
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }

        if ( szCmd != NULL )
        {
            delete[] szCmd;
            szCmd = NULL;
        }

        return hr;
    }
    RILSIMRECORDSTATUS GetStatus() const
    {
        return m_rsrs;
    }
    virtual void Dump() const
    {
#ifdef DEBUG
        _tprintf(TEXT("---RILSIMRECORDSTATUS[0x%04x]----------"), m_dwAddress);
        ATCmd::Dump();
        _tprintf(TEXT("cbSize       [%u]"), m_rsrs.cbSize);
        _tprintf(TEXT("dwParams     [%u]"), m_rsrs.dwParams);
        if ( m_rsrs.dwParams & RIL_PARAM_SRS_RECORDTYPE )
        {
            _tprintf(TEXT("dwRecordType [%u]"), m_rsrs.dwRecordType);
        }
        if ( m_rsrs.dwParams & RIL_PARAM_SRS_ITEMCOUNT )
        {
            _tprintf(TEXT("dwItemCount  [%u]"), m_rsrs.dwItemCount);
        }
        if ( m_rsrs.dwParams & RIL_PARAM_SRS_SIZE )
        {
            _tprintf(TEXT("dwSize       [%u]"), m_rsrs.dwSize);
        }
        _tprintf(TEXT("---RILSIMRECORDSTATUS[0x%04x]----------"), m_dwAddress);
#endif
    }
protected:
    virtual HRESULT Parse( LPCSTR szRsp )
    {
        void *pBlob = NULL;
        UINT cbBlob = 0;

        HRESULT hr = ParseGetSimRecordStatus( szRsp, pBlob, cbBlob );
        if ( SUCCEEDED( hr ) && pBlob != NULL && cbBlob == sizeof(m_rsrs) )
        {
            memcpy( &m_rsrs, pBlob, sizeof(m_rsrs) );
        }

        if ( pBlob != NULL )
        {
            FreeBlob( pBlob );
            pBlob = NULL;
        }

        return hr;
    }
private:
    DWORD m_dwAddress;
    RILSIMRECORDSTATUS m_rsrs;
};

class CRSMRecord : public ATCmd
{
public:
    CRSMRecord( DWORD dwAddress, DWORD dwIndex, DWORD dwSize )
    {
        m_dwAddress = dwAddress;
        m_dwIndex = dwIndex;
        m_dwSize = dwSize;
        m_prsr = NULL;
    }
    virtual ~CRSMRecord()
    {
        if ( m_prsr != NULL )
        {
            FreeBlob( m_prsr );
            m_prsr = NULL;
        }
    }
    HRESULT Read()
    {
        HRESULT hr = E_FAIL;

#ifdef WAVECOM_DRIVER
        /* WaveCom hardware doesnt support AT+CRSM */
        hr = E_NOTIMPL;
#else
        char *szCmd = NULL;
        char buf[128];

        /* based on misc.cpp:RILDrv_SendRestrictedSimCmd() */
        /* based on simrecrd.cpp:ReadRecord() */
        /* simplied for use in the EONS context */

        _snprintfz( buf, ARRAY_LENGTH(buf), "AT+CRSM=%u,%u,%u,%u,%u",
                    178, m_dwAddress, m_dwIndex, 4, m_dwSize );

        BOOL fOk = ComposeCmdWithByteArray( buf, NULL, 0, "\r", szCmd );
        if ( fOk )
        {
            hr = Execute( szCmd );
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }

        if ( szCmd != NULL )
        {
            delete[] szCmd;
            szCmd = NULL;
        }
#endif

        return hr;
    }
    RILSIMRESPONSE GetResponse() const
    {
        RILSIMRESPONSE rsr;
        memset( &rsr, 0, sizeof(rsr) );
        if ( m_prsr != NULL )
        {
            memcpy( &rsr, m_prsr, sizeof(rsr) );
        }
        return rsr;
    }
    BOOL GetRecord( __in_ecount(cbData) BYTE *pData, DWORD& cbData ) const
    {
        BOOL fOk = FALSE;
        if ( m_prsr != NULL && pData != NULL && cbData > 0 )
        {
            int totalsize = (int)m_prsr->cbSize;
            int responsesize = (int)sizeof(RILSIMRESPONSE);
            int recordsize = ( totalsize - responsesize );
            if ( totalsize > 0 && recordsize > 0 && cbData >= (DWORD)recordsize )
            {
                BYTE *pPtr = (BYTE*)m_prsr;

                memset( pData, 0, cbData );

                memcpy( pData, pPtr+responsesize, recordsize );
                cbData = (DWORD)recordsize;

                fOk = TRUE;
            }
        }
        return fOk;
    }
    virtual void Dump() const
    {
#ifdef DEBUG
        _tprintf(TEXT("---RILSIMRESPONSE[%u@0x%04x]----------"), m_dwIndex, m_dwAddress);
        ATCmd::Dump();
        if ( m_prsr != NULL )
        {
            _tprintf(TEXT("cbSize        [%u]"), m_prsr->cbSize);
            _tprintf(TEXT("dwParams      [%u]"), m_prsr->dwParams);
            if ( m_prsr->dwParams & RIL_PARAM_SR_STATUSWORD1 )
            {
                _tprintf(TEXT("dwStatusWord1 [%u]"), m_prsr->dwStatusWord1);
            }
            if ( m_prsr->dwParams & RIL_PARAM_SR_STATUSWORD2 )
            {
                _tprintf(TEXT("dwStatusWord2 [%u]"), m_prsr->dwStatusWord2);
            }
            if ( m_prsr->dwParams & RIL_PARAM_SR_RESPONSE )
            {
                _tprintf(TEXT("record length [%u]"), m_prsr->cbSize-sizeof(RILSIMRESPONSE));
            }
        }
        _tprintf(TEXT("---RILSIMRESPONSE[%u@0x%04x]----------"), m_dwIndex, m_dwAddress);
#endif
    }
protected:
    virtual HRESULT Parse( LPCSTR szRsp )
    {
        void *pBlob = NULL;
        UINT cbBlob = 0;

        HRESULT hr = ParseSendRestrictedSimCmd( szRsp, pBlob, cbBlob );
        if ( SUCCEEDED( hr ) && pBlob != NULL && cbBlob >= sizeof(RILSIMRESPONSE) )
        {
            m_prsr = static_cast<RILSIMRESPONSE*>( pBlob );
        }

        return hr;
    }
private:
    DWORD m_dwAddress;
    DWORD m_dwIndex;
    DWORD m_dwSize;
    RILSIMRESPONSE *m_prsr;
};

/*** end of helper ATCmd ****************************************************/
/****************************************************************************/

/* 3GPP TS 31.102 version 5.7.0 Release 5 sections 4.2.58 and 4.2.59 */
const DWORD EONS::EFopl = 0x6fc6;
const DWORD EONS::EFpnn = 0x6fc5;

const char EONS::WILDCARD = '*';
const char EONS::DONTCARE = 'x';

/* 3GPP TS 24.008 version 3.2.1 Release 1999 Table 9.2.18 */
const int EONS::IEIfullname  = 0x43;
const int EONS::IEIshortname = 0x45;

EONS::EONS()
{
    InitializeCriticalSection( &m_cs );
    m_hThread = NULL;
    m_fDoneReading = FALSE;
}

EONS::EONS( const EONS& ref )
    : m_opl( ref.m_opl ), m_pnn( ref.m_pnn )
{
    InitializeCriticalSection( &m_cs );
    m_hThread = NULL;
    m_fDoneReading = TRUE;    
}

EONS::~EONS()
{
    Clear();
    DeleteCriticalSection( &m_cs );
}

void
EONS::Clear()
{
    SYNCBLOCK( m_cs );

    m_fDoneReading = FALSE;

    m_opl.RemoveAllThings();
    m_pnn.RemoveAllThings();

    if ( m_hThread != NULL )
    {
        CloseHandle( m_hThread );
    }
    m_hThread = NULL;
}

BOOL
EONS::Ready() const
{
    BOOL fReady = FALSE;
    {
        BOOL fCS = TryEnterCriticalSection( &m_cs );
        if ( fCS )
        {
            fReady = m_fDoneReading;
            LeaveCriticalSection( &m_cs );
        }
    }
    return fReady;
}

BOOL
EONS::Search( DWORD dwPLMN, RILOPERATORNAMES *pRON ) const
{
    BOOL fMatched = Match( dwPLMN, 0xFFFFFFFF, pRON );
    return fMatched;
}

BOOL
EONS::Search( DWORD dwPLMN, DWORD dwLAC, RILOPERATORNAMES *pRON ) const
{
    BOOL fMatched = FALSE;
    if ( dwLAC != 0xFFFFFFFF )
    {
        fMatched = Match( dwPLMN, dwLAC, pRON );
    }
    return fMatched;
}

void
EONS::Dump() const
{
#ifdef DEBUG
    SYNCBLOCK( m_cs );
    _tprintf(TEXT("eons -- ready[%d]"), (int)m_fDoneReading );
    {
        int c = const_cast<GrowableThingCollection<OPLStruct>*>( &m_opl )->GetSize();
        for( int i = 0; i < c; i++ )
        {
            OPLStruct opl = const_cast<GrowableThingCollection<OPLStruct>*>( &m_opl )->GetThingAtIndex( i );
            _tprintf(TEXT("opl[%d] -- plmn [%hs] lacA [0x%04x] lacZ [0x%04x] pnn [%u]"), i+1, opl.szPLMN, opl.dwLACa, opl.dwLACz, opl.dwIndex );
        }
    }
    {
        int c = const_cast<GrowableThingCollection<RILOPERATORNAMES>*>( &m_pnn )->GetSize();
        for( int i = 0; i < c; i++ )
        {
            RILOPERATORNAMES ron = const_cast<GrowableThingCollection<RILOPERATORNAMES>*>( &m_pnn )->GetThingAtIndex( i );
            _tprintf(TEXT("pnn[%d] -- size [%u] param [%u]"), i+1, ron.cbSize, ron.dwParams);
            if ( ron.dwParams & RIL_PARAM_ON_LONGNAME )
            {
                _tprintf(TEXT("pnn[%d]    long [%hs]"), i+1, ron.szLongName);
            }
            if ( ron.dwParams & RIL_PARAM_ON_SHORTNAME )
            {
                _tprintf(TEXT("pnn[%d]    short [%hs]"), i+1, ron.szShortName);
            }
            if ( ron.dwParams & RIL_PARAM_ON_NUMNAME )
            {
                _tprintf(TEXT("pnn[%d]    num [%hs]"), i+1, ron.szNumName);
            }
            if ( ron.dwParams & RIL_PARAM_ON_COUNTRY_CODE )
            {
                _tprintf(TEXT("pnn[%d]    countrycode [%hs]"), i+1, ron.szCountryCode);
            }
        }
    }
#endif
}

HRESULT
EONS::Read()
{
    HRESULT hr = E_FAIL;
    {
        SYNCBLOCK( m_cs );
        if ( !m_fDoneReading )
        {
            if ( m_hThread == NULL )
            {
                m_hThread = CreateThread( NULL, 0, &EONS::s_ThreadStartCallback, (LPVOID)this, 0, NULL );
                if ( m_hThread != NULL )
                {
                    /* reading started */
                    hr = S_OK;
                }
            }
            else
            {
                /* reading in progress */
                hr = S_OK;
            }
        }
        else
        {
            /* reading complete */
            hr = S_OK;
        }
    }
    return hr;
}

DWORD
EONS::s_ThreadStartCallback( LPVOID lpThreadData )
{
    DWORD dw = 0;

    ASSERT( lpThreadData != NULL );
    if ( lpThreadData != NULL )
    {
        EONS *pEONS = reinterpret_cast<EONS*>( lpThreadData );

        ASSERT( pEONS != NULL );
        if ( pEONS != NULL )
        {
            HRESULT hr = pEONS->ReadData();
        }

        pEONS = NULL;
    }

    return dw;
}

HRESULT
EONS::ReadData()
{
    HRESULT hr = E_FAIL;
    {
        SYNCBLOCK( m_cs );

        hr = ReadOPLData();
        if ( SUCCEEDED( hr ) )
        {
            hr = ReadPNNData();
        }

        /* enforce data coherency */
        Validate();

        m_fDoneReading = TRUE;

        /* housekeeping */
        if ( m_hThread != NULL )
        {
            CloseHandle( m_hThread );
        }
        m_hThread = NULL;
    }
    return hr;
}

HRESULT
EONS::ReadOPLData()
{
    /* assumption -- m_cs has been acquired */

    HRESULT hr = E_FAIL;

    CRSMStatus k( EFopl );
    HRESULT hr2 = k.Read();
    k.Dump();
    if ( SUCCEEDED( hr2 ) )
    {
        RILSIMRECORDSTATUS rsrs = k.GetStatus();
        if ( ( rsrs.dwParams & RIL_PARAM_SRS_SIZE && rsrs.dwSize >= 8 ) &&
            ( rsrs.dwParams & RIL_PARAM_SRS_ITEMCOUNT ) )
        {
            const DWORD dwSize = rsrs.dwSize;
            const DWORD dwItemCount = rsrs.dwItemCount;

            char *pBuf = new char[ dwSize ];
            if ( pBuf != NULL )
            {
                for( int i = 1; i <= (int)dwItemCount; i++ )
                {
                    DWORD dw = 0;
                    memset( pBuf, 0, dwSize );

                    CRSMRecord kk( EFopl, i, dwSize );
                    hr2 = kk.Read();
                    kk.Dump();

                    if ( SUCCEEDED( hr2 ) )
                    {
                        dw = dwSize;
                        BOOL fOk = kk.GetRecord( (BYTE*)pBuf, dw );
                        hr2 = ( fOk ? S_OK : E_FAIL );
                    }

                    if ( SUCCEEDED( hr2 ) && pBuf[0] != (char)0xff )
                    {
                        const char *cptr = pBuf;

                        /* 3GPP TS 31.102 version 5.7.0 section 4.2.59 */

                        int b[6];
                        for( int j = 0; j < 3; j++ )
                        {
                            char x = pBuf[j];

                            b[j*2]   = ( 0x0f & ( x >> 4 ) );
                            b[j*2+1] = ( 0x0f & x );
                        }

                        /* BCD offset */
                        int bindex[] = { 1, 0, 3, 5, 4, 2 };

                        BOOL fDeleted = FALSE;

                        char szPLMN[ARRAY_LENGTH(b)+1];
                        for( int j = 0; j < ARRAY_LENGTH(b) && !fDeleted; j++ )
                        {
                            szPLMN[j] = NULL;
                            if ( 0 <= b[bindex[j]] && b[bindex[j]] < 10 )
                            {
                                /* digits 0-9 */
                                szPLMN[j] = ( '0'+b[bindex[j]] );
                            }
                            else if ( b[bindex[j]] == 0x0d )
                            {
                                /* wildcard */
                                szPLMN[j] = WILDCARD;
                            }
                            else if ( b[bindex[j]] == 0x0f )
                            {
                                /* dontcare */
                                szPLMN[j] = DONTCARE;
                            }
                            else
                            {
                                /* deleted entry */
                                fDeleted = TRUE;
                            }
                        }
                        szPLMN[ARRAY_LENGTH(szPLMN)-1] = NULL;

                        OPLStruct opl;
                        memset( &opl, 0, sizeof(opl) );

                        if ( !fDeleted )
                        {
                            long lacA = ( ( 0x0ff & pBuf[3] ) << 8 ) + ( 0x0ff & pBuf[4] );
                            long lacZ = ( ( 0x0ff & pBuf[5] ) << 8 ) + ( 0x0ff & pBuf[6] );
                            long idx  = ( 0x0ff & pBuf[7] );

                            ASSERT( idx > 0 );

                            strncpyz( opl.szPLMN, szPLMN, ARRAY_LENGTH(opl.szPLMN) );
                            opl.dwLACa  = lacA;
                            opl.dwLACz  = lacZ;
                            opl.dwIndex = idx;
                        }
                        else
                        {
                            /* index of 0 means deleted entry */
                            opl.dwIndex = 0;
                        }

                        m_opl.AddThing( opl );
                    }
                }

                hr = S_OK;
            }
            else
            {
                hr = E_OUTOFMEMORY;
            }

            if ( pBuf != NULL )
            {
                delete[] pBuf;
                pBuf = NULL;
            }
        }
    }

    return hr;
}

HRESULT
EONS::ReadPNNData()
{
    /* assumption -- m_cs has been acquired */

    HRESULT hr = E_FAIL;

    CRSMStatus k( EFpnn );
    HRESULT hr2 = k.Read();
    k.Dump();
    if ( SUCCEEDED( hr2 ) )
    {
        RILSIMRECORDSTATUS rsrs = k.GetStatus();
        if ( ( rsrs.dwParams & RIL_PARAM_SRS_SIZE ) &&
            ( rsrs.dwParams & RIL_PARAM_SRS_ITEMCOUNT ) )
        {
            const DWORD dwSize = rsrs.dwSize;
            const DWORD dwItemCount = rsrs.dwItemCount;

            char *pBuf = new char[ dwSize ];
            if ( pBuf != NULL )
            {
                for( int i = 1; i <= (int)dwItemCount; i++ )
                {
                    DWORD dw = 0;
                    memset( pBuf, 0, dwSize );

                    CRSMRecord kk( EFpnn, i, dwSize );
                    hr2 = kk.Read();
                    kk.Dump();

                    if ( SUCCEEDED( hr2 ) )
                    {
                        dw = dwSize;
                        BOOL fOk = kk.GetRecord( (BYTE*)pBuf, dw );
                        hr2 = ( fOk ? S_OK : E_FAIL );
                    }

                    RILOPERATORNAMES ron;
                    memset( &ron, 0, sizeof(ron) );
                    ron.cbSize = sizeof(ron);

                    if ( SUCCEEDED( hr2 ) && pBuf[0] != (char)0xff )
                    {
                        const char *cptr = pBuf;

                        /* 3GPP TS 31.102 version 5.7.0 section 4.2.58 */

                        const char *Ftlv = NULL;
                        int Fiei = 0;
                        int Flen = 0;

                        if ( cptr != NULL && dw >= 2 )
                        {
                            Fiei = (int)( 0x0ff & cptr[0] );
                            Flen = (int)( 0x0ff & cptr[1] );
                            if ( (int)dw >= 2+Flen )
                            {
                                Ftlv = cptr;

                                cptr += ( 2+Flen );
                                dw   -= ( 2+Flen );
                            }
                            else
                            {
                                cptr = NULL;
                                dw = 0;
                            }
                        }

                        const char *Stlv = NULL;
                        int Siei = 0;
                        int Slen = 0;

                        if ( cptr != NULL && dw >= 2 )
                        {
                            Siei = (int)( 0x0ff & cptr[0] );
                            Slen = (int)( 0x0ff & cptr[1] );
                            if ( (int)dw >= 2+Slen )
                            {
                                Stlv = cptr;
                            }
                        }

                        /* long name */
                        if ( Fiei == IEIfullname && Ftlv != NULL )
                        {
                            BOOL fLong = DecodeNetworkNameTLV( Ftlv, ron.szLongName, ARRAY_LENGTH(ron.szLongName) );
                            if ( fLong )
                            {
                                ron.dwParams |= RIL_PARAM_ON_LONGNAME;
                            }
                        }

                        /* short name */
                        if ( Siei == IEIshortname && Stlv != NULL )
                        {
                            BOOL fShort = DecodeNetworkNameTLV( Stlv, ron.szShortName, ARRAY_LENGTH(ron.szShortName) );
                            if ( fShort )
                            {
                                ron.dwParams |= RIL_PARAM_ON_SHORTNAME;
                            }
                        }
                    }

                    /* add to maintain PNN order regardless of read/parse status */
                    m_pnn.AddThing( ron );
                }

                hr = S_OK;
            }
            else
            {
                hr = E_OUTOFMEMORY;
            }

            if ( pBuf != NULL )
            {
                delete[] pBuf;
                pBuf = NULL;
            }
        }
    }

    return hr;
}

BOOL
EONS::DecodeNetworkNameTLV( const char *cptr, char *pszOUT, DWORD dwOut ) const
{
    BOOL fOk = FALSE;
    if ( cptr[0] != (char)0xff )
    {
        /* 3GPP TS 24.008 version 3.2.1 section 10.5.3.5a */

        int iei = ( 0x07f & cptr[0] );
        int len = ( 0x0ff & cptr[1] );
        int ext = ( 0x01 & ( cptr[2] >> 7 ) );
        int cs  = ( 0x07 & ( cptr[2] >> 4 ) );
        int ci  = ( 0x01 & ( cptr[2] >> 3 ) );
        int spr = ( 0x07 & cptr[2] );

#if 0
        _tprintf(TEXT("iei [%d]"), iei );
        _tprintf(TEXT("len [%d]"), len );
        _tprintf(TEXT("ext [%d] cs [%d] ci [%d] spr [%d]"), ext, cs, ci, spr );
#endif

        if ( len > 0 )
        {
            TCHAR szBuf[128];
            memset( szBuf, 0, sizeof(szBuf) );

            if ( cs == 0 )
            {
                /* cell broadcast data coding scheme, GSM default alphabet */
                /* GSM 03.38 */

                int offset = 3;
                const char *pcszA = cptr+offset;

                /* adjust */
                --len;

                UINT lenZ = 0;
                ConvertToUnicode( ENCODING_GSMDEFAULT, pcszA, (UINT)len, szBuf, ARRAY_LENGTH(szBuf), lenZ );
                pszOUT[lenZ] = 0;

                AnsiString as(szBuf);
                strncpyz( pszOUT, (const char *)as, dwOut );
            }
            else if ( cs == 1 )
            {
                /* UCS2 (16bit) */

                int offset = 3;
                const char *pcszA = cptr+offset;

                /* adjust */
                --len;

                ASSERT( len % 2 == 0 );

                int iNum = ( len / 2 );
                for( int i = 0; i < iNum && i < ARRAY_LENGTH(szBuf)-1; i++ )
                {
                    szBuf[i] = (TCHAR)( ( 0x0ff00 & ( pcszA[i*2] << 8 ) ) | ( 0x0ff & pcszA[i*2+1] ) );
                }

                AnsiString as(szBuf);
                strncpyz( pszOUT, (const char *)as, dwOut );
            }
            else
            {
                /* reserved coding scheme */
                ASSERT( FALSE );
                pszOUT[0] = 0;
            }

            fOk = ( pszOUT[0] != NULL );
        }
    }
    return fOk;
}

BOOL
EONS::Match( DWORD dwPLMN, DWORD dwLAC, RILOPERATORNAMES *pRON ) const
{
    BOOL fMatched = FALSE;

    BOOL fHasLAC = ( dwLAC != 0xFFFFFFFF );

    ASSERT( dwPLMN <= 999999 );
    ASSERT( dwLAC <= 0x0ffff || dwLAC == 0xFFFFFFFF );

    /* only support 6-digit PLMN */
    if ( dwPLMN <= 999999 && ( dwLAC <= 0x0ffff || dwLAC == 0xFFFFFFFF ) )
    {
        SYNCBLOCK( m_cs );
        if ( m_fDoneReading )
        {
            DWORD entries[2] = { dwPLMN, 0xFFFFFFFF };

            if ( dwPLMN <= 99999 )
            {
                /* normalize to 6-digit PLMN */
                /* this follows how network.cpp:GetLongOperatorName() expands 5-digit PLMN to 6-digit */

                DWORD YYY = ( dwPLMN / 100 );
                DWORD XX  = ( dwPLMN % 100 );

                entries[0] = ( YYY * 1000 + XX );   /* YYY0XX */
                entries[1] = ( dwPLMN * 10 );       /* YYYXX0 */
            }

            for( int i = 0; i < ARRAY_LENGTH(entries) && entries[i] != 0xFFFFFFFF && !fMatched; i++ )
            {
                DWORD dwEntry = entries[i];

                int digits[] =
                {
                    ( dwEntry / 100000 ),
                    ( ( dwEntry / 10000 ) % 10 ),
                    ( ( dwEntry / 1000 ) % 10 ),
                    ( ( dwEntry / 100 ) % 10 ),
                    ( ( dwEntry / 10 ) % 10 ),
                    ( dwEntry % 10 )
                };

#ifdef DEBUG
                /* at least as large as dwPLMN to catch problem with normalizing */
                ASSERT( dwEntry >= dwPLMN );
                /* should not process this marker entry */
                ASSERT( dwEntry != 0xFFFFFFFF );
                /* enforce 6-digit PLMN */
                ASSERT( dwEntry <= 999999 );
                /* ensure digits[] is setup right */
                ASSERT( ARRAY_LENGTH(digits) == 6 );
                for( int j = 0; j < ARRAY_LENGTH(digits); j++ )
                {
                    ASSERT( 0 <= digits[j] && digits[j] <= 9 );
                }
#endif

                int iPNN = -1;

                int c = const_cast<GrowableThingCollection<OPLStruct>*>( &m_opl )->GetSize();
                for( int j = 0; j < c && !fMatched; j++ )
                {
                    OPLStruct opl = const_cast<GrowableThingCollection<OPLStruct>*>( &m_opl )->GetThingAtIndex( j );

                    /* PLMN match */
                    BOOL fMatchedPLMN = TRUE;
                    for( int k = 0; k < 6 && fMatchedPLMN; k++ )
                    {
                        if ( opl.szPLMN[k] == WILDCARD )
                        {
                            /* always match */
                        }
                        else if ( opl.szPLMN[k] == DONTCARE )
                        {
                            /* 3GPP TS 24.008 version 3.2.1 Release 1999 Table 10.5.3 */
                            /* handling the case for YYYXX0 */
                            fMatchedPLMN = ( k == 5 && dwPLMN <= 99999 && digits[k] == 0 );
                        }
                        else
                        {
                            /* match exact digit */
                            fMatchedPLMN = ( opl.szPLMN[k]-'0' == digits[k] );
                        }
                    }

                    /* LAC match */
                    BOOL fMatchedLAC = ( opl.dwLACa == 0x0000 && opl.dwLACz == 0xfffe );
                    if ( !fMatchedLAC && fHasLAC )
                    {
                        fMatchedLAC = ( opl.dwLACa <= dwLAC && dwLAC <= opl.dwLACz );
                    }

                    fMatched = ( fMatchedPLMN && fMatchedLAC );
                    if ( fMatched )
                    {
                        iPNN = static_cast<int>( opl.dwIndex );

                        /* skip deleted entry */
                        fMatched = ( iPNN != 0 );
                    }
                }

                if ( fMatched )
                {
                    if ( iPNN > 0 )
                    {
                        int c2 = const_cast<GrowableThingCollection<RILOPERATORNAMES>*>( &m_pnn )->GetSize();
                        if ( iPNN-1 < c2 )
                        {
                            RILOPERATORNAMES ron = const_cast<GrowableThingCollection<RILOPERATORNAMES>*>( &m_pnn )->GetThingAtIndex( iPNN-1 );

                            /* consider matched IFF there is a long name */
                            fMatched = ( ( ron.dwParams & RIL_PARAM_ON_LONGNAME ) != 0 );

                            if ( fMatched && pRON != NULL )
                            {
                                memcpy( pRON, &ron, sizeof(RILOPERATORNAMES) );
                            }
                        }
                        else
                        {
                            ASSERT( FALSE );
                            fMatched = FALSE;
                        }
                    }
                    else
                    {
                        ASSERT( FALSE );
                        fMatched = FALSE;
                    }
                }
            }
        }
    }

    return fMatched;
}

void
EONS::Validate() const
{
#ifdef DEBUG
    /* normally everything from SIM should be fine, right? */
    {
        SYNCBLOCK( m_cs );

        int iOPL = const_cast<GrowableThingCollection<OPLStruct>*>( &m_opl )->GetSize();
        int iPNN = const_cast<GrowableThingCollection<RILOPERATORNAMES>*>( &m_pnn )->GetSize();

        if ( iOPL > 0 )
        {
            ASSERT( iPNN > 0 );
        }

        /* 3GPP TS 31.102 version 5.7.0 release 5 section 4.2.59 EFOPL */
        /* PNN record identifier is of size 1 byte -- maximum 2^8 PNN entries */
        ASSERT( 0 <= iPNN && iPNN < 256 );

        for( int i = 0; i < iOPL; i++ )
        {
            OPLStruct opl = const_cast<GrowableThingCollection<OPLStruct>*>( &m_opl )->GetThingAtIndex( i );
            for( int j = 0; j < (int)strlen(opl.szPLMN); j++ )
            {
                /* make sure the DONTCARE is in the MNC3 position */
                if ( opl.szPLMN[j] == DONTCARE )
                {
                    ASSERT( j == 5 );
                }
            }
        }

#ifdef CHECK
        for( int i = 0; i < iOPL; i++ )
        {
            OPLStruct opl = const_cast<GrowableThingCollection<OPLStruct>*>( &m_opl )->GetThingAtIndex( i );

            /* 6-digit PLMN for not-deleted entry */
            if ( opl.dwIndex != 0 )
            {
                ASSERT( strlen(opl.szPLMN) == 6 );
            }

            /* lacA <= lacZ */
            ASSERT( opl.dwLACa <= opl.dwLACz );

            /* lacZ <= 0x0ffff */
            ASSERT( opl.dwLACz <= 0x0ffff );

            /* PLMN should not be all WILDCARDs */
            BOOL fAllWildcards = TRUE;
            for( int j = 0; j < (int)strlen(opl.szPLMN) && fAllWildcards; j++ )
            {
                fAllWildcards = ( opl.szPLMN[j] == WILDCARD );
            }
            ASSERT( !fAllWildcards );

            /* index to PNN list */
            ASSERT( opl.dwIndex <= 256 );
            /* index can only be either 0 (deleted entry) or within [1,num-of-PNN-entry] */
            ASSERT( opl.dwIndex == 0 || ( 1 <= opl.dwIndex && opl.dwIndex <= static_cast<DWORD>( iPNN ) ) );
        }
        for( int i = 0; i < iPNN; i++ )
        {
            RILOPERATORNAMES ron = const_cast<GrowableThingCollection<RILOPERATORNAMES>*>( &m_pnn )->GetThingAtIndex( i );

            /* dont exist */
            ASSERT( ! ( ron.dwParams & RIL_PARAM_ON_COUNTRY_CODE ) );
            ASSERT( ! ( ron.dwParams & RIL_PARAM_ON_NUMNAME ) );
        }
#endif
    }
#endif
}

/****************************************************************************/
/****************************************************************************/

const TCHAR EONSCache::RegistryPath[] = RIL_SECURE_REGISTRY_KEY TEXT("\\EONS.cache");

EONSCache::EONSCache()
{
}

EONSCache::EONSCache( const EONS& ref )
    : EONS(ref)
{
}

EONSCache::~EONSCache()
{
}

HRESULT
EONSCache::Read()
{
    /* override to make the read synchronous */
    HRESULT hr = ReadData();
    return hr;
}

HRESULT
EONSCache::Write()
{
    HRESULT hr = WriteOPLData();
    if ( SUCCEEDED( hr ) )
    {
        hr = WritePNNData();
    }
    return hr;
}

void
EONSCache::Invalidate()
{
    Clear();
}

HRESULT
EONSCache::ReadOPLData()
{
    HRESULT hr = E_FAIL;

    HKEY hkey = NULL;

    StringBuffer key;
    key.Set( RegistryPath );
    key.Append( TEXT("\\opl") );

    LONG lResult = RegOpenKeyEx( RIL_REGISTRY_ROOT, key, 0, 0, &hkey );
    if ( lResult == ERROR_SUCCESS )
    {
        hr = S_OK;
        TCHAR subkey[16];

        BOOL fDone = FALSE;
        for( int i = 1; !fDone; i++ )
        {
            _sntprintfz( subkey, ARRAY_LENGTH(subkey), TEXT("%d"), i );

            HKEY hkey2 = NULL;

            lResult = RegOpenKeyEx( hkey, subkey, 0, 0, &hkey2 );
            if ( lResult == ERROR_SUCCESS )
            {
                OPLStruct opl;
                memset( &opl, 0, sizeof(opl) );

                BOOL fError = FALSE;
                DWORD dwSize = 0;
                DWORD dwType = 0;

                {
                    TCHAR szBuf[ARRAY_LENGTH(opl.szPLMN)];
                    dwSize = sizeof(szBuf);
                    lResult = RegQueryValueEx( hkey2, TEXT("PLMN"), NULL, &dwType, (BYTE*)szBuf, &dwSize );
                    fError = ( lResult != ERROR_SUCCESS || dwType != REG_SZ );
                    if ( !fError )
                    {
                        AnsiString as(szBuf);
                        strncpyz( opl.szPLMN, (const char*)as, ARRAY_LENGTH(opl.szPLMN) );
                    }
                }

                if ( !fError )
                {
                    dwSize = sizeof(opl.dwLACa);
                    lResult = RegQueryValueEx( hkey2, TEXT("start LAC"), NULL, &dwType, (BYTE*)&opl.dwLACa, &dwSize );
                    fError = ( lResult != ERROR_SUCCESS || dwType != REG_DWORD );
                }

                if ( !fError )
                {
                    dwSize = sizeof(opl.dwLACz);
                    lResult = RegQueryValueEx( hkey2, TEXT("end LAC"), NULL, &dwType, (BYTE*)&opl.dwLACz, &dwSize );
                    fError = ( lResult != ERROR_SUCCESS || dwType != REG_DWORD );
                }

                if ( !fError )
                {
                    dwSize = sizeof(opl.dwIndex);
                    lResult = RegQueryValueEx( hkey2, TEXT("index"), NULL, &dwType, (BYTE*)&opl.dwIndex, &dwSize );
                    fError = ( lResult != ERROR_SUCCESS || dwType != REG_DWORD );
                }

                if ( !fError )
                {
                    m_opl.AddThing( opl );
                }
            }
            else if ( lResult == ERROR_FILE_NOT_FOUND )
            {
                fDone = TRUE;
                hr = S_OK;
            }
            else
            {
                fDone = TRUE;
                hr = HRESULT_FROM_WIN32( lResult );
            }

            if ( hkey2 != NULL )
            {
                RegCloseKey( hkey2 );
            }
            hkey2 = NULL;
        }
    }
    else if ( lResult == ERROR_FILE_NOT_FOUND )
    {
        /* cache doesnt exist; treat as no items */
        hr = S_OK;
    }
    else
    {
        hr = HRESULT_FROM_WIN32( lResult );
    }

    if ( hkey != NULL )
    {
        RegCloseKey( hkey );
    }
    hkey = NULL;

    return hr;
}

HRESULT
EONSCache::ReadPNNData()
{
    HRESULT hr = E_FAIL;

    HKEY hkey = NULL;

    StringBuffer key;
    key.Set( RegistryPath );
    key.Append( TEXT("\\pnn") );

    LONG lResult = RegOpenKeyEx( RIL_REGISTRY_ROOT, key, 0, 0, &hkey );
    if ( lResult == ERROR_SUCCESS )
    {
        hr = S_OK;
        TCHAR subkey[16];

        BOOL fDone = FALSE;
        for( int i = 1; !fDone; i++ )
        {
            _sntprintfz( subkey, ARRAY_LENGTH(subkey), TEXT("%d"), i );

            HKEY hkey2 = NULL;

            lResult = RegOpenKeyEx( hkey, subkey, 0, 0, &hkey2 );
            if ( lResult == ERROR_SUCCESS )
            {
                RILOPERATORNAMES ron;
                memset( &ron, 0, sizeof(ron) );
                ron.cbSize = sizeof(ron);

                BOOL fError = FALSE;
                DWORD dwSize = 0;
                DWORD dwType = 0;

                {
                    TCHAR szBuf[ARRAY_LENGTH(ron.szLongName)];
                    dwSize = sizeof(szBuf);
                    lResult = RegQueryValueEx( hkey2, TEXT("long name"), NULL, &dwType, (BYTE*)szBuf, &dwSize );
                    fError = ( lResult != ERROR_SUCCESS || dwType != REG_SZ );
                    if ( !fError )
                    {
                        AnsiString as(szBuf);
                        strncpyz( ron.szLongName, (const char*)as, ARRAY_LENGTH(ron.szLongName) );
                        ron.dwParams |= RIL_PARAM_ON_LONGNAME;
                    }
                }

                if ( !fError )
                {
                    TCHAR szBuf[ARRAY_LENGTH(ron.szShortName)];
                    dwSize = sizeof(szBuf);
                    lResult = RegQueryValueEx( hkey2, TEXT("short name"), NULL, &dwType, (BYTE*)szBuf, &dwSize );
                    if ( lResult == ERROR_SUCCESS && dwType == REG_SZ )
                    {
                        AnsiString as(szBuf);
                        strncpyz( ron.szShortName, (const char*)as, ARRAY_LENGTH(ron.szShortName) );
                        ron.dwParams |= RIL_PARAM_ON_SHORTNAME;
                    }
                    else if ( lResult == ERROR_FILE_NOT_FOUND )
                    {
                        /* ok -- short name is optional */
                    }
                    else
                    {
                        fError = TRUE;
                    }
                }

                if ( !fError )
                {
                    m_pnn.AddThing( ron );
                }
            }
            else if ( lResult == ERROR_FILE_NOT_FOUND )
            {
                fDone = TRUE;
                hr = S_OK;
            }
            else
            {
                fDone = TRUE;
                hr = HRESULT_FROM_WIN32( lResult );
            }

            if ( hkey2 != NULL )
            {
                RegCloseKey( hkey2 );
            }
            hkey2 = NULL;
        }
    }
    else if ( lResult == ERROR_FILE_NOT_FOUND )
    {
        /* cache doesnt exist; treat as no items */
        hr = S_OK;
    }
    else
    {
        hr = HRESULT_FROM_WIN32( lResult );
    }

    if ( hkey != NULL )
    {
        RegCloseKey( hkey );
    }
    hkey = NULL;

    return hr;
}

HRESULT
EONSCache::WriteOPLData()
{
    HRESULT hr = S_OK;

    StringBuffer key;
    key.Set( RegistryPath );
    key.Append( TEXT("\\opl") );

    LONG lResult = RegDeleteKey( RIL_REGISTRY_ROOT, key );

    int c = m_opl.GetSize();
    if ( c > 0 )
    {
        HKEY hkey = NULL;
        DWORD dwDisposition = 0;
        lResult = RegCreateKeyEx( RIL_REGISTRY_ROOT, key, 0, NULL, 0, 0, NULL, &hkey, &dwDisposition );
        if ( lResult == ERROR_SUCCESS )
        {
            TCHAR subkey[16];
            BOOL fError = FALSE;

            for( int i = 0; i < c && !fError; i++ )
            {
                _sntprintfz( subkey, ARRAY_LENGTH(subkey), TEXT("%d"), i+1 );

                HKEY hkey2 = NULL;
                lResult = RegCreateKeyEx( hkey, subkey, 0, NULL, 0, 0, NULL, &hkey2, &dwDisposition );
                if ( lResult == ERROR_SUCCESS )
                {
                    OPLStruct opl = m_opl.GetThingAtIndex( i );

                    DWORD dwSize = 0;

                    {
                        WideString ws( opl.szPLMN );
                        dwSize = ( ( strlen(opl.szPLMN) + 1 ) * sizeof(TCHAR) );
                        lResult = RegSetValueEx( hkey2, TEXT("PLMN"), 0, REG_SZ, (BYTE*)(const TCHAR *)ws, dwSize );
                        if ( lResult != ERROR_SUCCESS )
                        {
                            fError = TRUE;
                            hr = HRESULT_FROM_WIN32( lResult );
                        }
                    }

                    if ( !fError )
                    {
                        dwSize = sizeof(opl.dwLACa);
                        lResult = RegSetValueEx( hkey2, TEXT("start LAC"), 0, REG_DWORD, (BYTE*)&opl.dwLACa, dwSize );
                        if ( lResult != ERROR_SUCCESS )
                        {
                            fError = TRUE;
                            hr = HRESULT_FROM_WIN32( lResult );
                        }
                    }

                    if ( !fError )
                    {
                        dwSize = sizeof(opl.dwLACz);
                        lResult = RegSetValueEx( hkey2, TEXT("end LAC"), 0, REG_DWORD, (BYTE*)&opl.dwLACz, dwSize );
                        if ( lResult != ERROR_SUCCESS )
                        {
                            fError = TRUE;
                            hr = HRESULT_FROM_WIN32( lResult );
                        }
                    }

                    if ( !fError )
                    {
                        dwSize = sizeof(opl.dwIndex);
                        lResult = RegSetValueEx( hkey2, TEXT("index"), 0, REG_DWORD, (BYTE*)&opl.dwIndex, dwSize );
                        if ( lResult != ERROR_SUCCESS )
                        {
                            fError = TRUE;
                            hr = HRESULT_FROM_WIN32( lResult );
                        }
                    }
                }
                else
                {
                    fError = TRUE;
                    hr = HRESULT_FROM_WIN32( lResult );
                }

                if ( hkey2 != NULL )
                {
                    RegCloseKey( hkey2 );
                }
                hkey2 = NULL;
            }
        }
        else
        {
            hr = HRESULT_FROM_WIN32( lResult );
        }

        if ( hkey != NULL )
        {
            RegCloseKey( hkey );
        }
        hkey = NULL;
    }
    
    return hr;
}

HRESULT
EONSCache::WritePNNData()
{
    HRESULT hr = S_OK;

    StringBuffer key;
    key.Set( RegistryPath );
    key.Append( TEXT("\\pnn") );

    LONG lResult = RegDeleteKey( RIL_REGISTRY_ROOT, key );

    int c = m_pnn.GetSize();
    if ( c > 0 )
    {
        HKEY hkey = NULL;
        DWORD dwDisposition = 0;
        lResult = RegCreateKeyEx( RIL_REGISTRY_ROOT, key, 0, NULL, 0, 0, NULL, &hkey, &dwDisposition );
        if ( lResult == ERROR_SUCCESS )
        {
            TCHAR subkey[16];
            BOOL fError = FALSE;

            for( int i = 0; i < c && !fError; i++ )
            {
                _sntprintfz( subkey, ARRAY_LENGTH(subkey), TEXT("%d"), i+1 );

                HKEY hkey2 = NULL;
                lResult = RegCreateKeyEx( hkey, subkey, 0, NULL, 0, 0, NULL, &hkey2, &dwDisposition );
                if ( lResult == ERROR_SUCCESS )
                {
                    RILOPERATORNAMES ron = m_pnn.GetThingAtIndex( i );

                    DWORD dwSize = 0;

                    if ( ron.dwParams & RIL_PARAM_ON_LONGNAME )
                    {
                        WideString ws( ron.szLongName );
                        dwSize = ( ( strlen(ron.szLongName) + 1 ) * sizeof(TCHAR) );
                        lResult = RegSetValueEx( hkey2, TEXT("long name"), 0, REG_SZ, (BYTE*)(const TCHAR *)ws, dwSize );
                        if ( lResult != ERROR_SUCCESS )
                        {
                            fError = TRUE;
                            hr = HRESULT_FROM_WIN32( lResult );
                        }
                    }

                    if ( !fError )
                    {
                        if ( ron.dwParams & RIL_PARAM_ON_SHORTNAME )
                        {
                            WideString ws( ron.szShortName );
                            dwSize = ( ( strlen(ron.szShortName) + 1 ) * sizeof(TCHAR) );
                            lResult = RegSetValueEx( hkey2, TEXT("short name"), 0, REG_SZ, (BYTE*)(const TCHAR *)ws, dwSize );
                            if ( lResult != ERROR_SUCCESS )
                            {
                                fError = TRUE;
                                hr = HRESULT_FROM_WIN32( lResult );
                            }
                        }
                    }
                }
                else
                {
                    fError = TRUE;
                    hr = HRESULT_FROM_WIN32( lResult );
                }

                if ( hkey2 != NULL )
                {
                    RegCloseKey( hkey2 );
                }
                hkey2 = NULL;
            }
        }
        else
        {
            hr = HRESULT_FROM_WIN32( lResult );
        }

        if ( hkey != NULL )
        {
            RegCloseKey( hkey );
        }
        hkey = NULL;
    }
    
    return hr;
}
